﻿//  Author:
//       Daniel Großer <olav.daniel.grosser@googlemail.com>
//  
//  Copyright (c) 2012 Copyright (C) 2012 by Daniel Großer. This Code is under GNU LGPL 3.0
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using Beauty.Contract;
using System.ComponentModel;

namespace Beauty.ErrorHandling
{
    /// <summary>
    /// This exception is thrown whenever the parser encounters a problem.
    /// </summary>
    public class ParserException : Exception
    {
        /// <summary>
        /// Get the line where the error occurred.
        /// </summary>
        public uint Line { get; private set; }

        /// <summary>
        /// Get the column where the error occurred.
        /// </summary>
        public uint Column { get; private set; }

        /// <summary>
        /// Create a new instance.
        /// </summary>
        /// <param name="message">The message of the exception</param>
        internal ParserException(string message)
            : this(null, message)
        {
        }

        /// <summary>
        /// Create a new instance.
        /// </summary>
        /// <param name="scanner">The scanner that was used.</param>
        /// <param name="message">The message of the exception</param>
        private ParserException(IScanner scanner, string message) : 
            base(scanner == null ? message : string.Format("[{0}:{1}] {2}", scanner.Line, scanner.Column, message))            
        {
            if (scanner != null)
            {
                Line = scanner.Line;
                Column = scanner.Column;
            }
        }

        /// <summary>
        /// Create a new instance.
        /// </summary>
        /// <param name="scanner">The scanner that was used.</param>
        /// <param name="error">The error that occurred.</param>
        internal ParserException(ParserErrorType error, params object[] formatArguments)
            : this(null, error, formatArguments)
        {
        }
        
        /// <summary>
        /// Create a new instance.
        /// </summary>
        /// <param name="scanner">The scanner that was used.</param>
        /// <param name="error">The error that occurred.</param>
        /// <param name="formatArguments">Arguments to use for string-formatting the error description.</param>
        internal ParserException(IScanner scanner, ParserErrorType error, params object[] formatArguments)
            : this(scanner, GetErrorDescription(error, formatArguments))
        {
        }

        /// <summary>
        /// Create a new instance.
        /// </summary>
        /// <param name="scanner">The scanner that was used.</param>
        /// <param name="expectedTerminals">The set of expected terminals that was not met.</param>
        internal ParserException(IScanner scanner, HashSet<TerminalAttribute> expectedTerminals)
            : this(scanner, CreateSyntaxErrorMessage(scanner, expectedTerminals))
        {
        }
		
		/// <summary>
		/// Creates the syntax error message.
		/// </summary>
		/// <returns>
		/// The syntax error message.
		/// </returns>
		/// <param name='scanner'>
		/// The scanner currently used. Yields current terminal.
		/// </param>
		/// <param name='expectedTerminals'>
		/// The expected terminals.
		/// </param>
		private static string CreateSyntaxErrorMessage(IScanner scanner, HashSet<TerminalAttribute> expectedTerminals)
		{		
			return GetErrorDescription(ParserErrorType.SyntaxError, scanner.CurrentToken, string.Join(", ", expectedTerminals.ToList().ConvertAll<string>((s)=>s.ToString()).ToArray()));
		}

        /// <summary>
        /// Get the value of the DescriptionAttribute on a ParserErrorType instance.
        /// </summary>
        /// <param name="error">The ParserErrorType instance.</param>
        /// <param name="formatArguments">Arguments to use for string-formatting the error description.</param>
        /// <returns>The value of the DescriptionAttribute.</returns>
        private static string GetErrorDescription(ParserErrorType error, params object[] formatArguments)
        {
            var member = typeof(ParserErrorType).GetMember(Enum.GetName(typeof(ParserErrorType), error))[0];
            var description = (DescriptionAttribute)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute));
            return string.Format(description.Description, formatArguments);
        }
    }
}
